﻿using System;
using System.Diagnostics;
using System.Globalization;
using Adventure.Server.Data;
using Adventure.Server.Library;
using Adventure.Server.Manager;
using Adventure.Server.Properties;
using Neitzel;
using Neitzel.Network;

namespace Adventure.Server
{
    /// <summary>
    /// AdventureServer instance.
    /// </summary>
    public class AdventureServer : IDisposable
    {
        #region Fields
        
        /// <summary>
        /// Tracesource for all log messages.
        /// </summary>
        private static readonly TraceSource Logger = new TraceSource(ServerConstants.ServerTraceSourceNameApplication);

        /// <summary>
        /// Data Adapter of the server.
        /// </summary>
        private IDataAdapter _dataAdapter;

        /// <summary>
        /// The server connection for the game server.
        /// </summary>
        private TcpIpServerConnection _serverConnection;

        /// <summary>
        /// Object manager for the game server.
        /// </summary>
        private IObjectManager _objectManager;

        #endregion

        #region Lifetime

        /// <summary>
        /// Finalizer
        /// </summary>
        ~AdventureServer()
        {
            Dispose(false);
        }

        /// <summary>
        /// Dispose this instance.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Dispose this instance.
        /// </summary>
        /// <param name="disposing">Do a full dispose or not?</param>
        protected virtual void Dispose(bool disposing)
        {
            // We only have managed stuff to dispose
            if (!disposing) return;

            _serverConnection?.Close();
            _serverConnection = null;
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Starts the server.
        /// </summary>
        public void Start()
        {
            // Get the data adapter
            _dataAdapter = LoadDataAdapter(Settings.Default.DataAdapterClass, Settings.Default.DataSource);

            // Get the object manager
            _objectManager = LoadObjectManager(Settings.Default.ObjectManagerClass);
            SavedObject.SetObjectManager(_objectManager);

            _serverConnection = new TcpIpServerConnection(Settings.Default.Port);
            _serverConnection.Open();
        }

        /// <summary>
        /// Close / dispose / stop the server.
        /// </summary>
        public void Close()
        {
            Dispose();
        }

        /// <summary>
        /// Close / dispose / stop the server.
        /// </summary>
        public void Stop()
        {
            Dispose();
        }

        /// <summary>
        /// Load the data adapter with the given type and initialize it with the source.
        /// </summary>
        /// <param name="className">Classname of the DataAdapter to load.</param>
        /// <param name="source">Source of the the data adapter.</param>
        /// <returns>The data adapter.</returns>
        public static IDataAdapter LoadDataAdapter(string className, string source)
        {
            // validate arguments
            if (string.IsNullOrEmpty(className)) throw new ArgumentNullException(nameof(className));

            // Load the data adapter
            Logger.TraceData(TraceEventType.Information, 0,
                string.Format(CultureInfo.InvariantCulture, "Loading DataAdapter from class {0} and setting source {1}.", className, source));

            IDataAdapter adapter = null;
            try
            {
                adapter = TypeExtensions.CreateInstance<IDataAdapter>(className);
            }
            catch (Exception ex)
            {
                var message = string.Format(CultureInfo.InvariantCulture,
                    Resources.UnableToLoadDataAdapterError, className);
                Logger.TraceData(TraceEventType.Critical, (int)ServerTraceId.Managers, message);
                throw new TypeLoadException(message, ex);
            }

            // Set the source
            adapter.SetSource(source);

            // return the adapter
            return adapter;
        }

        /// <summary>
        /// Load the object manager with the given type.
        /// </summary>
        /// <param name="className">Classname of the ObjectManager to load.</param>
        /// <returns>The object manager.</returns>
        public static IObjectManager LoadObjectManager(string className)
        {
            // validate arguments
            if (string.IsNullOrEmpty(className)) throw new ArgumentNullException(nameof(className));

            // Load the data adapter
            Logger.TraceData(TraceEventType.Information, 0,
                string.Format(CultureInfo.InvariantCulture, "Loading ObjectManager from class {0}.", className));

            IObjectManager adapter = null;
            try
            {
                adapter = TypeExtensions.CreateInstance<IObjectManager>(className);
            }
            catch (Exception ex)
            {
                var message = string.Format(CultureInfo.InvariantCulture,
                    Resources.UnableToLoadObjectManagerError, className);
                Logger.TraceData(TraceEventType.Critical, (int)ServerTraceId.Managers, message);
                throw new TypeLoadException(message, ex);
            }

            // return the adapter
            return adapter;
        }

        #endregion
    }
}
